﻿#include "DAAbstractNodeGraphicsItem.h"
#include <QPainter>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include "DANodeGraphicsScene.h"
#include "DANodePalette.h"
#include "DAAbstractNodeLinkGraphicsItem.h"
#include "DAAbstractNodeWidget.h"
#include "DAAbstractNode.h"
#include <QPointer>
////////////////////////////////////////////////////////////////////////

class _LinkData
{
public:
    _LinkData();
    _LinkData(DAAbstractNodeLinkGraphicsItem *i, const DANodeLinkPoint &p);
    DAAbstractNodeLinkGraphicsItem *linkitem;
    DANodeLinkPoint point;
};

bool operator==(const _LinkData &a, const _LinkData &b);

bool operator==(const _LinkData &a, const _LinkData &b)
{
    return ((a.linkitem == b.linkitem) && (a.point == b.point));
}

_LinkData::_LinkData() : linkitem(nullptr) {}

_LinkData::_LinkData(DAAbstractNodeLinkGraphicsItem *i, const DANodeLinkPoint &p) : linkitem(i), point(p) {}

class DAAbstractNodeGraphicsItemPrivate
{
    DA_IMPL_PUBLIC(DAAbstractNodeGraphicsItem)
public:
    DAAbstractNodeGraphicsItemPrivate(DAAbstractNode *n, DAAbstractNodeGraphicsItem *p);
    std::weak_ptr<DAAbstractNode> _node;
    QList<DANodeLinkPoint> _linkPoints;
    QPointer<DAAbstractNodeWidget> _nodeWidget; ///< 记录节点的窗口指针
    QList<_LinkData> _linkDatas; ///< 这里记录所有的link
};

DAAbstractNodeGraphicsItemPrivate::DAAbstractNodeGraphicsItemPrivate(DAAbstractNode *n, DAAbstractNodeGraphicsItem *p)
    : q_ptr(p), _node(n->pointer())
{
}

////////////////////////////////////////////////////////////////////////

DAAbstractNodeGraphicsItem::DAAbstractNodeGraphicsItem(DAAbstractNode *n, QGraphicsItem *p)
    : QAbstractGraphicsShapeItem(p), d_ptr(new DAAbstractNodeGraphicsItemPrivate(n, this))
{
    setFlags(flags() | ItemIsSelectable | ItemIsMovable | ItemSendsGeometryChanges //确保位置改变时能发出QGraphicsItem::ItemPositionHasChanged
    );
    setAcceptHoverEvents(true);
    n->registGraphicsItem(this);
}

DAAbstractNodeGraphicsItem::~DAAbstractNodeGraphicsItem()
{
    // item在删除时要通知相关的link把记录删除，否则会出现问题
    for (const _LinkData &ld : d_ptr->_linkDatas) {
        ld.linkitem->callItemIsDestroying(this, ld.point);
    }
}

/**
 * @brief 获取item对应的node
 * @return
 */
DAAbstractNode *DAAbstractNodeGraphicsItem::node()
{
    return (d_ptr->_node.lock().get());
}

const DAAbstractNode *DAAbstractNodeGraphicsItem::node() const
{
    return (d_ptr->_node.lock().get());
}

QString DAAbstractNodeGraphicsItem::getNodeName() const
{
    if (const DAAbstractNode *n = node()) {
        return (n->getNodeName());
    }
    return (QString());
}

QIcon DAAbstractNodeGraphicsItem::getIcon() const
{
    if (const DAAbstractNode *n = node()) {
        return (n->getIcon());
    }
    return (QIcon());
}

/**
 * @brief 设置图标
 * @param icon
 */
void DAAbstractNodeGraphicsItem::setIcon(const QIcon &icon)
{
    if (DAAbstractNode *n = node()) {
        n->setIcon(icon);
    }
}

/**
 * @brief 获取节点元数据
 * @return
 */
const DANodeMetaData &DAAbstractNodeGraphicsItem::metaData() const
{
    return (node()->metaData());
}

/**
 * @brief 获取节点元数据
 * @return
 */
DANodeMetaData &DAAbstractNodeGraphicsItem::metaData()
{
    return (node()->metaData());
}

/**
 * @brief 获取连接点群
 * @return
 */
const QList<DANodeLinkPoint> &DAAbstractNodeGraphicsItem::getLinkPoints() const
{
    return (d_ptr->_linkPoints);
}

/**
 * @brief 获取连接点
 * @param name
 * @return
 */
DANodeLinkPoint DAAbstractNodeGraphicsItem::getLinkPoint(const QString &name) const
{
    const QList<DANodeLinkPoint> &lps = getLinkPoints();

    for (const DANodeLinkPoint &lp : lps) {
        if (lp == name) {
            return (lp);
        }
    }
    return (DANodeLinkPoint());
}

/**
 * @brief 判断是否存在连接点
 * @param pl
 * @return
 */
bool DAAbstractNodeGraphicsItem::isHaveLinkPoint(const DANodeLinkPoint &pl) const
{
    const QList<DANodeLinkPoint> &lps = getLinkPoints();

    for (const DANodeLinkPoint &p : lps) {
        if (p == pl) {
            return (true);
        }
    }
    return (false);
}

/**
 * @brief 绘制默认连接点
 *
 * 每个连接点的绘制调用@sa paintLinkPoint 函数,因此实例化重载应该重载@sa paintLinkPoint 函数
 */
void DAAbstractNodeGraphicsItem::paintLinkPoints(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);
    const QList<DANodeLinkPoint> &lps = getLinkPoints();

    for (const DANodeLinkPoint &lp : lps) {
        paintLinkPoint(lp, painter);
    }
}

/**
 * @brief 绘制某个连接点
 * @param pl
 * @param painter
 */
void DAAbstractNodeGraphicsItem::paintLinkPoint(const DANodeLinkPoint &pl, QPainter *painter)
{
    painter->save();
    //连接点是一个长方形，6X8,点中心是长方形中心
    //先把painter坐标变换到点处
    QColor clr = DANodePalette::getGlobalLinkPointColor();
    QRect pointrange = getlinkPointRect(pl); // 横版矩形，对应East，West

    painter->setPen(clr);
    painter->drawRect(pointrange);
    if (DANodeLinkPoint::Output == pl.way) {
        painter->fillRect(pointrange, clr);
    }
    painter->restore();
}

/**
 * @brief 获取连接点对应的矩形区域
 * @param pl
 * @return
 */
QRect DAAbstractNodeGraphicsItem::getlinkPointRect(const DANodeLinkPoint &pl) const
{
    switch (pl.direction) {
    case DANodeLinkPoint::East:
    case DANodeLinkPoint::West:
        return (QRect(pl.position.x() - 4, pl.position.y() - 3, 8, 6));

    case DANodeLinkPoint::North:
    case DANodeLinkPoint::South:
        return (QRect(pl.position.x() - 3, pl.position.y() - 4, 6, 8));

    default:
        break;
    }
    return (QRect(pl.position.x() - 3, pl.position.y() - 3, 6, 6));
}

DAAbstractNodeWidget *DAAbstractNodeGraphicsItem::getNodeWidget() const
{
    return (d_ptr->_nodeWidget.data());
}

void DAAbstractNodeGraphicsItem::setNodeWidget(DAAbstractNodeWidget *p)
{
    if (p) {
        p->setNodeItem(this);
    }
    d_ptr->_nodeWidget = p;
}

void DAAbstractNodeGraphicsItem::resetLinkPoint()
{
    d_ptr->_linkPoints = generateLinkPoint();
}

/**
 * @brief 节点的动作将会调用此函数，例如节点调用input，等参数会反应到item上面，就通过此函数进行反应，
 * 通过重载此函数可以进行一些特殊的绘图
 * @param action
 * @sa FCAbstractNode::NodeAction
 * @param v
 */
void DAAbstractNodeGraphicsItem::nodeAction(int action, const QVariant &v)
{
    return;
}

/**
 * @brief 处理一些联动事件，如和FCAbstractNodeLinkGraphicsItem的联动
 * @param change
 * @param value
 * @return
 */
QVariant DAAbstractNodeGraphicsItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
    if ((ItemPositionHasChanged == change) && scene()) {
        // 变化了位置,需要更新link item
        for (const _LinkData &ld : d_ptr->_linkDatas) {
            ld.linkitem->updatePos();
        }
    }
    return (QGraphicsItem::itemChange(change, value));
}

/**
 * @brief 鼠标按下
 * @param event
 */
void DAAbstractNodeGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    QList<DANodeLinkPoint> lps = getLinkPoints();

    //左键点击
    for (const DANodeLinkPoint &lp : lps) {
        if (getlinkPointRect(lp).contains(event->pos().toPoint())) {
            //说明这个link point点击了
            //这时候调用sence的信号
            DANodeGraphicsScene *sc = qobject_cast<DANodeGraphicsScene *>(scene());
            if (nullptr == sc) {
                qDebug() << "FCAbstractNodeGraphicsItem must be add in FCNodeGraphicsScene";
                return; //异常直接退出
            }
            // scene发射nodeItemLinkPointSelected信号
            sc->callNodeItemLinkPointSelected(this, lp, event);
            //处理完一个就跳出
            return;
        }
    }
}

/**
 * @brief 此函数用于FCAbstractNodeLinkGraphicsItem在调用attachedTo/From过程中调用
 * @param item
 * @param pl
 * @return 如果返回false，说明记录不成功，已经有相同的连接了
 */
bool DAAbstractNodeGraphicsItem::recordLink(DAAbstractNodeLinkGraphicsItem *link, const DANodeLinkPoint &pl)
{
    _LinkData d(link, pl);

    if (d_ptr->_linkDatas.contains(d)) {
        return (false);
    }

    d_ptr->_linkDatas.append(d);
    return (true);
}

/**
 * @brief 连接的link在销毁时调用，把item记录的link信息消除
 * @param item
 * @param pl
 * @return
 */
bool DAAbstractNodeGraphicsItem::callItemLinkIsDestroying(DAAbstractNodeLinkGraphicsItem *link, const DANodeLinkPoint &pl)
{
    _LinkData d(link, pl);

    return (d_ptr->_linkDatas.removeAll(d) > 0);
}

/**
 * @brief 此函数根据FCAbstractNode的输入输出来生成默认的连接点
 *
 * 会调用@sa FCAbstractNode::inputKeys 获取所有的输入参数,
 * 调用@sa FCAbstractNode::outputKeys 判断是否生成输出节点
 *
 * 默认所有输入位于左边，所有输出位于右边
 * @return
 */
QList<DANodeLinkPoint> DAAbstractNodeGraphicsItem::generateLinkPoint() const
{
    QList<DANodeLinkPoint> res;
    const DAAbstractNode *n = node();
    QList<QString> ks = n->getInputKeys();

    QRectF br = boundingRect();

    //生成输入点
    if (ks.size() > 0) {
        //避免除0
        qreal x = br.left() + 4;
        qreal dy = (br.height() - 6) / (ks.size() + 1.0);
        qreal y = br.top() + 3 + dy;
        for (const QString &k : ks) {
            DANodeLinkPoint lp;
            lp.direction = DANodeLinkPoint::West;
            lp.way = DANodeLinkPoint::Input;
            lp.name = k;
            lp.position = QPoint(x, y);
            res.append(lp);
            y += dy;
        }
    }
    //生成输出点
    ks = n->getOutputKeys();

    if (ks.size() > 0) {
        //避免除0
        qreal x = br.right() - 4;
        qreal dy = (br.height() - 6) / (ks.size() + 1.0);
        qreal y = br.top() + 3 + dy;
        for (const QString &k : ks) {
            DANodeLinkPoint lp;
            lp.direction = DANodeLinkPoint::East;
            lp.way = DANodeLinkPoint::Output;
            lp.name = k;
            lp.position = QPoint(x, y);
            res.append(lp);
            y += dy;
        }
    }
    return (res);
}
